package org.spring.boot.example.dds;

import java.lang.reflect.Method;
import java.util.List;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

public class DynamicDataSourceAdvice implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		String dataSourceName = resoleDataSourceName(invocation);
		String oldDataSourceName = DataSourceThreadLocal.getCurrentDataSourceName();

		if (dataSourceName == null || dataSourceName.isEmpty() || dataSourceName.equals(oldDataSourceName)) {
			return invocation.proceed();
		}

		DataSourceThreadLocal.setCurrentDataSourceName(dataSourceName);
		Object result = null;
		BackupResourcesHolder resourcesHolder = null;
		try {
			// 如果当前线程开启了事务， 并且不是当前数据源则进行备份，防止继续传播下去
			if (TransactionSynchronizationManager.isSynchronizationActive()) {
				// 备份
				resourcesHolder = backupTransactionResources();

			}
			result = invocation.proceed();
		} finally {
			DataSourceThreadLocal.setCurrentDataSourceName(oldDataSourceName);

			if (resourcesHolder != null) {
				// 恢复
				restoreTransactionResources(resourcesHolder);
			}
		}

		return result;
	}

	/**
	 * 解析数据源名称注解
	 * 
	 * @param invocation
	 * @return
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 */
	protected String resoleDataSourceName(MethodInvocation invocation) throws NoSuchMethodException, SecurityException {
		Method method = invocation.getMethod();
		DataSourceName annotation = null;

		// 目标类方法 > 目标类 > 接口方法 > 接口类
		Object target = invocation.getThis();
		if (target != null) {
			Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
			annotation = targetMethod.getAnnotation(DataSourceName.class);
			if (annotation == null) {
				annotation = target.getClass().getAnnotation(DataSourceName.class);
			}
		}

		// 目标类无法获取注解， 则解析接口上的注解
		if (annotation == null) {
			// 如果是JDK代理， 方法则是接口的方法， 如果获取到注解将应用
			annotation = invocation.getMethod().getAnnotation(DataSourceName.class);
			if (annotation == null) {
				annotation = invocation.getClass().getAnnotation(DataSourceName.class);
			}
		}

		return annotation == null ? null : annotation.value();
	}

	/**
	 * 备份当前线程的事务信息
	 * 
	 * @return
	 */
	protected BackupResourcesHolder backupTransactionResources() {
		List<TransactionSynchronization> suspendedSynchronizations = TransactionSynchronizationManager
				.getSynchronizations();
		try {
			String name = TransactionSynchronizationManager.getCurrentTransactionName();
			TransactionSynchronizationManager.setCurrentTransactionName(null);
			boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
			Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
			boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
			TransactionSynchronizationManager.clear();
			return new BackupResourcesHolder(suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
		} catch (RuntimeException | Error ex) {
			throw ex;
		}
	}

	/**
	 * 恢复事务信息
	 * 
	 * @param resourcesHolder
	 */
	protected void restoreTransactionResources(BackupResourcesHolder resourcesHolder) {
		TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
		TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
		TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
		TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);

		List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
		if (suspendedSynchronizations != null) {
			TransactionSynchronizationManager.initSynchronization();
			suspendedSynchronizations.forEach(TransactionSynchronizationManager::registerSynchronization);
		}
	}

	protected class BackupResourcesHolder {

		private List<TransactionSynchronization> suspendedSynchronizations;

		private String name;

		private boolean readOnly;

		private Integer isolationLevel;

		private boolean wasActive;

		private BackupResourcesHolder(List<TransactionSynchronization> suspendedSynchronizations, String name,
				boolean readOnly, Integer isolationLevel, boolean wasActive) {

			this.suspendedSynchronizations = suspendedSynchronizations;
			this.name = name;
			this.readOnly = readOnly;
			this.isolationLevel = isolationLevel;
			this.wasActive = wasActive;
		}
	}

}
